package at.ac.tuwien.big.me12.csv.gen

import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.IFileSystemAccess
import org.eclipse.xtext.generator.IGenerator
import org.eclipse.emf.ecore.EObject
import at.ac.tuwien.big.me12.csv.csvmm.FileDefinitionSet
import at.ac.tuwien.big.me12.csv.csvmm.FileDefinition
import at.ac.tuwien.big.me12.csv.csvmm.StaticField
import at.ac.tuwien.big.me12.csv.csvmm.DerivedField
import at.ac.tuwien.big.me12.csv.csvmm.AggregationField
import at.ac.tuwien.big.me12.csv.csvmm.RuleBasedField
import at.ac.tuwien.big.me12.csv.csvmm.AggregationType
import at.ac.tuwien.big.me12.csv.csvmm.ComparisonRule
import at.ac.tuwien.big.me12.csv.csvmm.CompositeRule
import at.ac.tuwien.big.me12.csv.csvmm.ComparisonOperator
import at.ac.tuwien.big.me12.csv.csvmm.LogicalOperator
import at.ac.tuwien.big.me12.csv.csvmm.FieldType
import at.ac.tuwien.big.me12.csv.csvmm.AggregationField
import at.ac.tuwien.big.me12.csv.csvmm.RuleBasedField

class CSVAPIGenerator implements IGenerator {
	private static String PATH = "at/ac/tuwien/big/me12/csv/gen/";
	private static String PACKAGE = "at.ac.tuwien.big.me12.csv.gen"
	override doGenerate(Resource resource, IFileSystemAccess fsa) {
		 for (EObject eObject: resource.contents) {
            switch(eObject) {
                FileDefinitionSet : eObject.generateFile(fsa)
            }
        }
	}
	
	def generateFile(FileDefinitionSet set, IFileSystemAccess fsa) {
        for (FileDefinition definition : set.fileDefinitions) {
            fsa.generateFile(PATH + definition.name + "CSVReader.java", definition.generateReaderCode)
            fsa.generateFile(PATH + definition.name + "CSVWriter.java", definition.generateWriterCode)
            fsa.generateFile(PATH + definition.name + "CSVFile.java", definition.generateFileCode)
            fsa.generateFile(PATH + definition.name + "CSVLine.java", definition.generateLineCode)
            
        }
    }
    
    def generateReaderCode(FileDefinition fileDefinition) '''
		package «PACKAGE»;
		
		import java.io.*;
		
		public class «fileDefinition.name.toFirstUpper»CSVReader {
			private FileReader fr;
			private String currentLine;
			private String[] splitArray = null;
			
			public «fileDefinition.name.toFirstUpper»CSVReader(FileReader fr){
				this.fr = fr;
			}
			
			public «fileDefinition.name.toFirstUpper»CSVFile readFile(){
				«fileDefinition.name.toFirstUpper»CSVFile retFile = new «fileDefinition.name.toFirstUpper»CSVFile();
				«fileDefinition.name.toFirstUpper»CSVLine csvLine = null;
				try {
					BufferedReader data = new BufferedReader(fr);
					while ((currentLine = data.readLine()) != null) {
						splitArray = currentLine.split(",");
						csvLine = new «fileDefinition.name.toFirstUpper»CSVLine();
						
						int i = 0;
						«FOR fields : fileDefinition.fields»
							«IF fields instanceof StaticField»
								«IF fields.fieldType == FieldType::INTEGER»
									csvLine.set«fields.name»(Integer.parseInt(splitArray[i++].trim()));
								«ELSE»
									csvLine.set«fields.name»(splitArray[i++].trim());
								«ENDIF»
							«ENDIF»
						«ENDFOR»
						
						retFile.add(csvLine);
					}
				} catch (FileNotFoundException e) {
					System.out.println("ERROR: File Not Found!");
				} catch (IOException e) {
					System.out.println("ERROR: IO");
				}
				
				return retFile;
			}
		}
    '''
    
     def generateWriterCode(FileDefinition fileDefinition) '''
		package «PACKAGE»;
		
		import java.io.*;
		import java.util.List;
		
		public class «fileDefinition.name.toFirstUpper»CSVWriter {
			private FileWriter fw;
			
			public «fileDefinition.name.toFirstUpper»CSVWriter(FileWriter fw){
				this.fw = fw;
			}
			
			public void writeFile(«fileDefinition.name.toFirstUpper»CSVFile «fileDefinition.name.toLowerCase»CSVFile, boolean writeDerived){
				
				try {
					BufferedWriter out = new BufferedWriter(fw);
		
					List<«fileDefinition.name.toFirstUpper»CSVLine> fileList = meCSVFile.getLines();
					
					for («fileDefinition.name.toFirstUpper»CSVLine line : fileList) {
						«FOR fields : fileDefinition.fields»
							«IF fields instanceof StaticField»
								out.write("," + line.get«fields.name»());
							«ENDIF»
						«ENDFOR»
						
						if(writeDerived) {
							«FOR fields : fileDefinition.fields»
									«IF fields instanceof RuleBasedField && fields.fieldType == FieldType::BOOLEAN»
										out.write("," + line.is«fields.name»());
									«ELSEIF fields instanceof RuleBasedField || fields instanceof AggregationField»
										out.write("," + line.get«fields.name»());
									«ENDIF»
							«ENDFOR»
						}
						
						out.newLine();
					}
					
					out.close();
				} catch (IOException e) {
					System.out.println("ERROR: IO EXCEPTION");
				}
			}
		}
    '''
    
    def generateFileCode(FileDefinition fileDefinition) '''
		package «PACKAGE»;
		
		import java.util.*;
		
		public class «fileDefinition.name.toFirstUpper»CSVFile {
			private List<«fileDefinition.name.toFirstUpper»CSVLine> lines;
			
			public «fileDefinition.name.toFirstUpper»CSVFile(){
				lines = new ArrayList<«fileDefinition.name.toFirstUpper»CSVLine>();
			}
			
			public «fileDefinition.name.toFirstUpper»CSVFile(List<«fileDefinition.name.toFirstUpper»CSVLine> lines){
				this.lines = lines;
			}
			
			public void add(«fileDefinition.name.toFirstUpper»CSVLine element){
				lines.add(element);
			}
			
			public List<«fileDefinition.name.toFirstUpper»CSVLine> getLines(){
				return lines;
			}
		}
    '''
    
    def generateLineCode(FileDefinition fileDefinition) '''
		package «PACKAGE»;
		
		public class «fileDefinition.name.toFirstUpper»CSVLine {
			«FOR fields : fileDefinition.fields»
				«IF fields instanceof StaticField»
					«var sf = fields as StaticField»
					«sf.generateStaticFieldDefinition»
				«ELSEIF fields instanceof DerivedField»
					«var df = fields as DerivedField»
					«df.generateDerivedFieldDefinition»
				«ENDIF»
			«ENDFOR»
		}
    '''
        
     def generateStaticFieldDefinition(StaticField fieldDefinition) '''
     	«IF fieldDefinition.fieldType == FieldType::INTEGER»
    		private int «fieldDefinition.name» = 0;
    		public void set«fieldDefinition.name»(int value){
    			this.«fieldDefinition.name» = value;
    		}
    		
    		public int get«fieldDefinition.name»(){
    			return this.«fieldDefinition.name»;
    		}
    	«ELSEIF fieldDefinition.fieldType == FieldType::STRING»
    		private «fieldDefinition.fieldType.toString.toLowerCase.toFirstUpper» «fieldDefinition.name» = "";
    		public void set«fieldDefinition.name»(«fieldDefinition.fieldType.toString.toLowerCase.toFirstUpper» value){
    			this.«fieldDefinition.name» = value;
    		}
    		
    		public «fieldDefinition.fieldType.toString.toLowerCase.toFirstUpper» get«fieldDefinition.name»(){
    			return this.«fieldDefinition.name»;
    		}
    	«ELSEIF fieldDefinition.fieldType == FieldType::BOOLEAN»
			private «fieldDefinition.fieldType.toString.toLowerCase» «fieldDefinition.name» = false;
    		public void set«fieldDefinition.name»(«fieldDefinition.fieldType.toString.toLowerCase» value){
    			this.«fieldDefinition.name» = value;
    		}
    		
    		public «fieldDefinition.fieldType.toString.toLowerCase» get«fieldDefinition.name»(){
    			return this.«fieldDefinition.name»;
    		}
    	«ENDIF»
    '''
    
    def generateDerivedFieldDefinition(DerivedField fieldDefinition) '''
		«IF fieldDefinition instanceof AggregationField»
			«IF fieldDefinition.fieldType == FieldType::INTEGER»
				public int get«fieldDefinition.name»(){
			«ELSE»
				public «fieldDefinition.fieldType.toString.toLowerCase.toFirstUpper» get«fieldDefinition.name»(){
			«ENDIF»
				«var af = fieldDefinition as AggregationField»
				«af.generateAggregationFieldCode»
			}
		«ELSEIF fieldDefinition instanceof RuleBasedField»
			«var rbf = fieldDefinition as RuleBasedField»
			«rbf.generateRuleBasedFieldCode»
		«ENDIF»
    '''
    
    def generateAggregationFieldCode(AggregationField field)'''
    	«field.fieldType.toString.toLowerCase.toFirstUpper» ret = 0;
    	int[] value = new int[«field.aggregatedFields.size»]; 
    	int i = 0;
    	«FOR fields : field.aggregatedFields»
    			value[i++] = get«fields.name»();
    	«ENDFOR»
    	
    	«IF field.aggType == AggregationType::SUM»
			for(Integer v : value){
				ret += v;
			}
    	«ELSEIF field.aggType == AggregationType::AVERAGE»
			int sum = 0;
			for(Integer v : value){
				sum += v;
			}
			
			if(0 < value.length) {
			ret = Math.round((float)sum/value.length);
			}
    	«ELSEIF field.aggType == AggregationType::MIN»
    		ret = value[0];
			for(Integer v : value){
				if(v < ret){
					ret = v;
				}
			}
    	«ELSEIF field.aggType == AggregationType::MAX»
			for(Integer v : value){
				if(v > ret){
					ret = v;
				}
			}
    	«ENDIF»
    	return ret;
    '''
    
    def generateRuleBasedFieldCode(RuleBasedField field)'''
    	«FOR rule : field.rules»
    		«IF rule instanceof ComparisonRule»
				«var cr = rule as ComparisonRule»
					«cr.generateComparisonRule(field.name + "RULE", field.rules.indexOf(rule)) »
			«ELSEIF rule instanceof CompositeRule»
				«var cr = rule as CompositeRule»
				«cr.generateCompositeRule(field.name + "RULE", field.rules.indexOf(rule))»
			«ENDIF»
    	«ENDFOR»
    	
    	«IF field.fieldType == FieldType::BOOLEAN»
    		public «field.fieldType.toString.toLowerCase.toFirstUpper» is«field.name»(){
    	«ELSE»
    		public «field.fieldType.toString.toLowerCase.toFirstUpper» get«field.name»(){
    	«ENDIF»
    	«FOR rule : field.rules»
				if(is«field.name»RULE_«field.rules.indexOf(rule)»_Fulfilled()){
					«IF field.fieldType == FieldType::STRING»
						return new String("«rule.resultValue»");
					«ELSE»
						return «rule.resultValue»;
					«ENDIF»
				}
    	«ENDFOR»
    	
    	«IF field.defaultValue == null && field.fieldType == FieldType::BOOLEAN»
    		return false;
    	«ELSE»
	    	«IF field.fieldType == FieldType::STRING»
				return new String("«field.defaultValue»");
			«ELSE»
				return «field.defaultValue»;
			«ENDIF»
		«ENDIF»
    	}
    '''
    
    def generateComparisonRule(ComparisonRule rule, String name, Integer position)'''
    	private Boolean is«name»_«position»_Fulfilled(){
    	«IF rule.inputField.fieldType == FieldType::BOOLEAN»
	    	«switch rule.operator{
	    		case ComparisonOperator::EQUALS : 
	    			'''		return is«rule.inputField.name»() == «rule.comparisonValue»;'''
	    		case ComparisonOperator::UNEQUALS : 
	    			'''		return is«rule.inputField.name»() != «rule.comparisonValue»;'''
	    		case ComparisonOperator::GREATER : 
	    			'''		return is«rule.inputField.name»() > «rule.comparisonValue»;'''
	    		case ComparisonOperator::GREATER_OR_EQUALS : 
	    			'''		return is«rule.inputField.name»() >= «rule.comparisonValue»;'''
	    		case ComparisonOperator::LOWER : 
	    			'''		return is«rule.inputField.name»() < «rule.comparisonValue»;'''
	    		case ComparisonOperator::LOWER_OR_EQUALS : 
	    			'''		return is«rule.inputField.name»() == «rule.comparisonValue»;'''	
	    	}»
    	«ELSE»
    		«switch rule.operator{
	    		case ComparisonOperator::EQUALS : 
	    			'''		return get«rule.inputField.name»() == «rule.comparisonValue»;'''
	    		case ComparisonOperator::UNEQUALS : 
	    			'''		return get«rule.inputField.name»() != «rule.comparisonValue»;'''
	    		case ComparisonOperator::GREATER : 
	    			'''		return get«rule.inputField.name»() > «rule.comparisonValue»;'''
	    		case ComparisonOperator::GREATER_OR_EQUALS : 
	    			'''		return get«rule.inputField.name»() >= «rule.comparisonValue»;'''
	    		case ComparisonOperator::LOWER : 
	    			'''		return get«rule.inputField.name»() < «rule.comparisonValue»;'''
	    		case ComparisonOperator::LOWER_OR_EQUALS : 
	    			'''		return get«rule.inputField.name»() == «rule.comparisonValue»;'''	
	    	}»
    	«ENDIF»
    	
    	}
    '''

    
    def generateCompositeRule(CompositeRule rule, String name, Integer position)'''
    «FOR r : rule.rules»
		«IF r instanceof ComparisonRule»
			«var cr = r as ComparisonRule»
				«cr.generateComparisonRule(name.concat("_").concat(position.toString) , rule.rules.indexOf(r)) »
		«ELSEIF r instanceof CompositeRule»
			«var cr = r as CompositeRule»
			«cr.generateCompositeRule(name.concat("_").concat(position.toString), rule.rules.indexOf(r))»
		«ENDIF»
    «ENDFOR»
    
    private Boolean is«name»_«position»_Fulfilled(){
    	boolean ret = false;
    	boolean[] values = new boolean[«rule.rules.size»];
    	int i = 0;
    	«FOR r : rule.rules»
    		values[i++] = is«name»_«position»_«rule.rules.indexOf(r)»_Fulfilled();
    	«ENDFOR»
    	
    	«switch rule.operator{
    		case LogicalOperator::AND :
    			'''
    			ret=true;
    			for(Boolean b : values){
    				ret = ret && b;
    			}
    			'''
    		case LogicalOperator::OR :
    			'''
    			ret=false;
    			for(Boolean b : values){
    				ret = ret || b;
    			}
    			'''
    		case LogicalOperator::NOT :
    			'''
    			ret=ture;
    			for(Boolean b : values){
    				ret = not b;
    			}
    			'''
    		
    	}»
    	return ret;
    }	
    '''
    
}